﻿//--------------------------------------------------------------------------
// <copyright file="Jsons.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>Jsons</summary>
//--------------------------------------------------------------------------
namespace MS.Msn.MediaSolutions.Common.Web
{
    using System;
    using System.Web;
    using System.Text;
    using System.Xml;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Xml.XPath;

    /// <summary>
    /// Jsons
    /// </summary>
    public static class Jsons
    {
        #region public functions

        /// <summary>
        /// convert an XML file to JSON format string
        /// </summary>
        /// <param name="filePath">XML file path</param>
        /// <returns>JSON string</returns>
        public static string XmlFileToJSON(string filePath)
        {
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(filePath);
            return XmlToJSON(xDoc);
        }

        /// <summary>
        /// convert an XML string variable to JSON format string
        /// </summary>
        /// <param name="xmlContent">XML string</param>
        /// <returns>JSON string</returns>
        public static string XmlToJSON(string xmlContent)
        {
            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(xmlContent);
            return XmlToJSON(xDoc);
        }

        /// <summary>
        /// convert an XML document object to JSON format string
        /// </summary>
        /// <param name="xmlDoc">XML document object</param>
        /// <returns>JSON string</returns>
        public static string XmlToJSON(IXPathNavigable xmlDoc)
        {
            if (xmlDoc != null)
            {
                StringBuilder sbJSON = new StringBuilder();
                sbJSON.Append("{");
                XmlToJSONnode(sbJSON, ((XmlDocument)xmlDoc).DocumentElement, true);
                sbJSON.Append("}");
                return sbJSON.ToString();
            }
            else
            {
                throw new ArgumentNullException(Apps.GetArgNullExString("xmlDoc"));
            }
        }

        #endregion

        #region private functions

        //  XmlToJSONnode:  Output an XmlElement, possibly as part of a higher array
        private static void XmlToJSONnode(StringBuilder sbJSON, XmlElement node, bool showNodeName)
        {
            if (showNodeName)
            {
                sbJSON.Append(string.Format(Apps.DefaultCulture, "\"{0}\":", SafeJSON(node.Name)));
            }

            sbJSON.Append("{");
            // Build a sorted list of key-value pairs
            //  where   key is case-sensitive nodeName
            //          value is an ArrayList of string or XmlElement
            //  so that we know whether the nodeName is an array or not.
            SortedList childNodeNames = new SortedList();

            //  Add in all node attributes
            if (node.Attributes != null)
            {
                foreach (XmlAttribute attr in node.Attributes)
                {
                    StoreChildNode(childNodeNames, "$" + attr.Name, attr.InnerText);
                }
            }

            //  Add in all nodes
            foreach (XmlNode cnode in node.ChildNodes)
            {
                if (cnode is XmlText)
                {
                    StoreChildNode(childNodeNames, "$", cnode.InnerText);
                }
                else if (cnode is XmlElement)
                {
                    StoreChildNode(childNodeNames, cnode.Name, cnode);
                }
            }

            // Now output all stored info
            foreach (string childname in childNodeNames.Keys)
            {
                ArrayList alChild = (ArrayList)childNodeNames[childname];
                if (alChild.Count == 1)
                {
                    OutputNode(childname, alChild[0], sbJSON, true);
                }
                else
                {
                    sbJSON.Append(string.Format(Apps.DefaultCulture, "\"{0}\":[", SafeJSON(childname)));
                    foreach (object Child in alChild)
                    {
                        OutputNode(childname, Child, sbJSON, false);
                    }
                    sbJSON.Remove(sbJSON.Length - 1, 1);
                    sbJSON.Append("],");
                }
            }
            sbJSON.Remove(sbJSON.Length - 1, 1);
            sbJSON.Append("}");
        }

        //  StoreChildNode: Store data associated with each nodeName
        //                  so that we know whether the nodeName is an array or not.
        private static void StoreChildNode(SortedList childNodeNames, string nodeName, object nodeValue)
        {
            // Pre-process contraction of XmlElement-s
            if (nodeValue is XmlElement)
            {
                // Convert  <aa></aa> into "aa":null
                //          <aa>xx</aa> into "aa":"xx"
                XmlNode cnode = (XmlNode)nodeValue;
                if (cnode.Attributes.Count == 0)
                {
                    XmlNodeList children = cnode.ChildNodes;
                    if (children.Count == 0)
                    {
                        nodeValue = null;
                    }
                    else if (children.Count == 1 && (children[0] is XmlText))
                    {
                        nodeValue = ((XmlText)(children[0])).InnerText;
                    }
                }
            }
            // Add nodeValue to ArrayList associated with each nodeName
            // If nodeName doesn't exist then add it
            object oValuesAL = childNodeNames[nodeName];
            ArrayList ValuesAL;
            if (oValuesAL == null)
            {
                ValuesAL = new ArrayList();
                childNodeNames[nodeName] = ValuesAL;
            }
            else
            {
                ValuesAL = (ArrayList)oValuesAL;
            }
            ValuesAL.Add(nodeValue);
        }

        private static void OutputNode(string childname, object alChild, StringBuilder sbJSON, bool showNodeName)
        {
            string salChild = alChild as string;
            XmlElement xalChild = alChild as XmlElement;

            if (!object.Equals(salChild, null))
            {
                if (showNodeName)
                {
                    sbJSON.Append(string.Format(Apps.DefaultCulture, "\"{0}\":", SafeJSON(childname)));
                }
                sbJSON.Append(string.Format(Apps.DefaultCulture, "\"{0}\"", SafeJSON(salChild.Trim())));
            }
            else if (!object.Equals(xalChild, null))
            {
                XmlToJSONnode(sbJSON, xalChild, showNodeName);
            }
            else
            {
                if (showNodeName)
                {
                    sbJSON.Append(string.Format(Apps.DefaultCulture, "\"{0}\":", SafeJSON(childname)));
                }
                sbJSON.Append("null");
            }
            sbJSON.Append(",");
        }

        // Make a string safe for JSON
        private static string SafeJSON(string sIn)
        {
            StringBuilder sbOut = new StringBuilder(sIn.Length);
            foreach (char ch in sIn)
            {
                if (Char.IsControl(ch) || ch == '\'')
                {
                    int ich = (int)ch;
                    sbOut.Append(@"\u");
                    sbOut.Append(ich.ToString("x4", Apps.DefaultCulture));
                    continue;
                }
                else if (ch == '\"' || ch == '\\' || ch == '/')
                {
                    sbOut.Append('\\');
                }
                sbOut.Append(ch);
            }
            return sbOut.ToString();
        }

        #endregion
    }
}